import inspect
import os
import re
from typing import Union
from selenium.webdriver.common.by import By
import numpy as np
from appium import webdriver
from appium.webdriver import WebElement
from appium.webdriver.common.appiumby import AppiumBy
from selenium.common import StaleElementReferenceException
import cv2
from config import *
from common.Message import *
from common.UiSelector import *

# from skimage.metrics import structural_similarity as ssim

LocatorSpec = Union[tuple[By, str] or tuple[UiSelector, str] or list[tuple[UiSelector, str]]]
# 获取UiSelector所有属性
UiSelector_attr = [getattr(UiSelector, attr) for attr in dir(UiSelector) if
                   not callable(getattr(UiSelector, attr)) and not attr.startswith('__')]


class BasePage:
    screenshot_path = screenshot_path
    image_path = image_path

    def __init__(self, driver: webdriver.Remote):
        self.driver = driver
        self.windows_x = driver.get_window_size()['width']
        self.windows_y = driver.get_window_size()['height']
        print(self.windows_x, self.windows_y)
        if not os.path.exists(self.screenshot_path):
            os.mkdir(self.screenshot_path)
        if not os.path.exists(self.image_path):
            os.mkdir(self.image_path)

    def locator_by_uiautomator(self, loc: LocatorSpec, parent_number: int = 0,
                               element: WebElement = None) -> WebElement:
        """

        :param loc: 传递UiSelector定位器定位元素，可以是元组，也可以为元组列表，元组内下标为0的元素为UiSelector的属性值
        :param parent_number: 当传入的定位器为列表时，列表中按顺序排列的父元素选择器的个数
        :param element: 父元素，父元素结合UiSelector定位器定位子元素

        """

        if element:
            return element.find_element(AppiumBy.ANDROID_UIAUTOMATOR, UiSelector.uiautomator_code(loc, parent_number))
        return self.driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, UiSelector.uiautomator_code(loc, parent_number))

    def locator(self, loc: LocatorSpec, parent_number=0, element: WebElement = None) -> WebElement:
        if isinstance(loc[0], str) and loc[0] in UiSelector_attr or (
                isinstance(loc[0], tuple) and loc[0][0] in UiSelector_attr):
            return self.locator_by_uiautomator(loc, parent_number, element)
        return self.driver.find_element(*loc)

    def locators_by_uiautomator(self, loc: LocatorSpec, parent_number: int = 0,
                                element: WebElement = None):
        if element:
            return element.find_elements(AppiumBy.ANDROID_UIAUTOMATOR, UiSelector.uiautomator_code(loc, parent_number))
        return self.driver.find_elements(AppiumBy.ANDROID_UIAUTOMATOR, UiSelector.uiautomator_code(loc, parent_number))

    def locators(self, loc: LocatorSpec, parent_number: int = 0,
                 element: WebElement = None):
        if isinstance(loc[0], str) and loc[0] in UiSelector_attr or (
                isinstance(loc[0], tuple) and loc[0][0] in UiSelector_attr):
            return self.locators_by_uiautomator(loc, parent_number, element)
        return self.driver.find_elements(*loc)

    def click(self, loc: LocatorSpec, parent_number=0):
        return self.locator(loc, parent_number).click()

    def input(self, loc: LocatorSpec, value, parent_number=0):
        return self.locator(loc, parent_number).send_keys(value)

    def find_text(self, text, loc: LocatorSpec = None, parent_number=0):
        text_loc = (UiSelector.TEXT_CONTAINS, text)
        if not loc:
            return self.locator(text_loc, parent_number)
        if isinstance(loc, list):
            loc.append(text_loc)
            return self.locator(loc, parent_number)
        return self.locator([loc, text_loc], parent_number)

    def click_text(self, text, loc: LocatorSpec = None, parent_number=0):
        return self.find_text(text, loc, parent_number).click()

    def flick(self, x, y, dx, dy):
        return self.driver.flick(x, y, dx, dy)

    def loop_click(self, loc: tuple[By, str], times):
        locators = self.locators(loc)
        if locators is []:
            print(NO_SELECT)
            return
        for i in range(times):
            if i < len(locators):
                try:
                    locators[i].click()
                except StaleElementReferenceException:
                    locators = self.locators(loc)
                    locators[i].click()
                    continue

    def cv(self, img1, img2):
        # # 算法求出两张图片差异值similarity_index
        # similarity_index, diff = ssim(img1, img2, multichannel=True, full=True)
        # # 将差异图归一化到 0-255 范围，以便于保存
        # diff = (diff * 255).astype("uint8")
        # # 保存差异图
        # cv2.imwrite('./screenshots/diff_image.jpg', diff)

        # 另一个算法求两张图片差异值err
        err = np.sum((img1.astype("float") - img2.astype("float")) ** 2)
        err /= float(img1.shape[0] * img2.shape[1])
        print(f'{IMG_ERR}{err}')
        return err

    def current_cv(self, loc: LocatorSpec, func, parent_number=0):

        self.locator(loc, parent_number).screenshot(f'{self.screenshot_path}/{loc[1]}_1.jpg')
        img1 = cv2.imread(f'{self.screenshot_path}/{loc[1]}_1.jpg')
        func()
        self.locator(loc, parent_number).screenshot(f'{self.screenshot_path}/{loc[1]}_2.jpg')
        img2 = cv2.imread(f'{self.screenshot_path}/{loc[1]}_2.jpg')
        return self.cv(img1, img2)

    def version_cv(self, loc: LocatorSpec, parent_number=0):

        if not os.path.exists(f'{self.screenshot_path}/{loc[1]}.jpg'):
            print(NO_BASE_IMG)
            self.locator(loc, parent_number).screenshot(f'{self.screenshot_path}/{loc[1]}.jpg')
            return
        img1 = cv2.imread(f'{self.screenshot_path}/{loc[1]}.jpg')
        self.locator(loc, parent_number).screenshot(f'{self.screenshot_path}/{loc[1]}.jpg')
        img2 = cv2.imread(f'{self.screenshot_path}/{loc[1]}.jpg')
        return self.cv(img1, img2)

    def scroll(self, loc1: LocatorSpec, loc2: LocatorSpec):
        return self.driver.scroll(self.locator(loc1), self.locator(loc2))

    # 获取屏幕匹配图片的坐标
    def get_match_xy(self, template_img, threshold = 0.8):
        threshold = threshold
        # 读取原始图像和目标图像
        if not os.path.exists(f'{self.image_path}/{template_img}'):
            print(f'{self.image_path}/{template_img}')
            print(f"{template_img}{NO_IMG}")
            raise template_img + NO_IMG
        template = cv2.imread(f'{self.image_path}/{template_img}')
        # self.driver.get_screenshot_as_file(f'{self.screenshot_path}/screenshot.jpg')
        # main_img = cv2.imread(f'{self.screenshot_path}/screenshot.jpg')
        main_img = cv2.imdecode(np.frombuffer(self.driver.get_screenshot_as_png(), dtype=np.uint8), cv2.IMREAD_COLOR)
        # 使用模板匹配函数进行图像匹配
        result = cv2.matchTemplate(main_img, template, cv2.TM_CCOEFF_NORMED)

        # 获取模板图片宽高
        h, w = template.shape[:2]
        # 获取所有匹配结果的中心坐标
        loc = np.where(result >= threshold)
        locations = [(int(pt[0].item() + w / 2), int(pt[1].item() + h / 2)) for pt in zip(*loc[::-1])]
        # 未匹配到任何坐标时返回
        if len(locations) == 0:
            return

        # 显示原始图像和匹配结果
        # for pt in zip(*loc[::-1]):
        #     cv2.rectangle(main_img, pt, (pt[0] + w, pt[1] + h), (0, 255, 0), 2)

        # new_width = int(main_img.shape[1] * 0.5)
        # new_height = int(main_img.shape[0] * 0.5)
        #
        # cv2.namedWindow('Matched Image', cv2.WINDOW_NORMAL)
        # cv2.resizeWindow('Matched Image', new_width, new_height)
        # cv2.imshow('Matched Image', main_img)
        return locations

    # 点击屏幕匹配图片的坐标
    def click_match_xy(self, template_img, template_index=-1,threshold = 0.8):
        # 获取当前调用栈
        stack = inspect.stack()
        # 打印调用者的信息
        caller_frame = stack[1]
        caller_name = caller_frame.function
        match_xy = self.get_match_xy(template_img, threshold)
        if not match_xy:
            print(f'{caller_name}-{template_img}{NO_MATCH_RESULT}')
            return
        if template_index == 'all':
            print(f'{caller_name}-{template_img}{EXECUTE_SUCCESS}')
            for xy in match_xy:
                self.driver.tap([xy])
            return True
        print(f'{caller_name}-{template_img}{EXECUTE_SUCCESS}')
        return self.driver.tap([match_xy[template_index]])
